package main

import (
	"fmt"
	"net"
	"strings"
	"sync"
	"time"
)

const (
	udpBufSize int = 1400

	udpPackageStartTag     uint16 = 0x159E
	udpPackageprotoVersion uint16 = 0x0001

	udpPackageShakeHandCmd uint16 = 0xABCD
	udpPackageWaveHandCmd  uint16 = 0xABCC
	udpPackageVideoTranCmd uint16 = 0xABCB
	udpPackageAudioTranCmd uint16 = 0xABCA

	macAddrBytes int = 13
	//maxClientNum   int = 7
	maxUploadNum   int = 9
	maxDownloadNum int = 7

	transitChanSize int = 4 * 1024
)

var (
	mapRwLock    sync.RWMutex
	macUdpTcpMap map[string]*MapValueType

	upDownRwLock sync.RWMutex
	upDownDatas  map[UpDownParaKeyType]*UpDownParaValueType

	udpListenConn net.UDPConn
)

func initUdp() {

}

func udpServer(listenPort int) {
	initUdp()
	listenBuf := fmt.Sprintf("0.0.0.0:%d", listenPort)
	udpAddr, err := net.ResolveUDPAddr("udp", listenBuf)
	checkError(err)
	conn, err := net.ListenUDP("udp", udpAddr)
	checkError(err)
	fmt.Println("udp server listening at", string(listenBuf[0:]))

	udpListenConn = *conn
	recvChan := make(chan RecvUdpChanDataType, 4*1024)

	go recvThread(conn, recvChan)
	parseThread(conn, recvChan)

	defer conn.Close()
}

func recvThread(conn *net.UDPConn, ch chan<- RecvUdpChanDataType) {
	var chanData RecvUdpChanDataType

	for {
		n, addr, err := conn.ReadFromUDP(chanData.buf[0:])
		if err != nil {
			//time.Sleep(1 * time.Second)
			continue
		}
		//fmt.Println("read udp package ! size=", n)

		chanData.addr = *addr
		chanData.packageSize = n
		ch <- chanData
	}

	defer fmt.Println("leave recvUDPData")
}

func parseThread(conn *net.UDPConn, ch <-chan RecvUdpChanDataType) {
	for {
		chanData := <-ch
		parseProtoData(conn, &chanData)
	}

	defer fmt.Println("leave dealBufData")
}

func parseProtoData(conn *net.UDPConn, pChanData *RecvUdpChanDataType) {
	packageSize := pChanData.packageSize
	b := pChanData.buf[0:]
	head := &ProtoHeadType{uint16(b[0]) | uint16(b[1])<<8, uint16(b[2]) | uint16(b[3])<<8, uint16(b[4]) | uint16(b[5])<<8, uint16(b[6]) | uint16(b[7])<<8}
	//fmt.Println("udpHead = ", head)

	if packageSize < protoHeadSize || packageSize > udpBufSize {
		fmt.Println("head.packageSize error ! ", packageSize)
		return
	}

	if head.tag != udpPackageStartTag {
		fmt.Printf("head.tag error ! tag = %x\n", head.tag)
		return
	}

	if head.bodySize != uint16(packageSize-protoHeadSize) {
		fmt.Println("head.bodySize error ! ", head.bodySize, packageSize)
		return
	}

	//fmt.Println("udp package legal !")

	switch head.cmd {
	case udpPackageVideoTranCmd:
		tranVideoServer(conn, pChanData)
	case udpPackageAudioTranCmd:
		tranAudioServer(conn, pChanData)
	case udpPackageShakeHandCmd:
		recvUdpShakeHandPackage(conn, pChanData)
	case udpPackageWaveHandCmd:
		recvUdpWaveHandPackage(conn, pChanData)
	default:
		fmt.Println("unknow udp package cmd ! ", head.cmd)
	}
}

func videoTransitThread(conn *net.UDPConn, ch <-chan []byte, upDownData *UpDownParaValueType) {
	for upDownData.busy == true {
		data := <-ch
		if upDownData.video == false {
			continue
		}

		for i := range upDownData.downLoadDatas {
			e := &(upDownData.downLoadDatas[i])
			if e.busy == true && e.video == true {
				n, err := conn.WriteToUDP(data, &(e.udpAddr))
				if err != nil {
					fmt.Println("transit video data fail ! err = ", err.Error(), "n = ", n)
					continue
				}
			}
		}
	}

	defer fmt.Println("leave videoTransitThread")
}

func audioTransitThread(conn *net.UDPConn, ch <-chan []byte, upDownData *UpDownParaValueType) {
	for upDownData.busy == true {
		data := <-ch
		if upDownData.audio == false {
			continue
		}

		for i := range upDownData.downLoadDatas {
			e := &(upDownData.downLoadDatas[i])
			if e.busy == true && e.audio == true {
				n, err := conn.WriteToUDP(data, &(e.udpAddr))
				if err != nil {
					fmt.Println("transit audio data fail ! err = ", err.Error(), "n = ", n)
					continue
				}
			}
		}
	}

	defer fmt.Println("leave audioTransitThread")
}

func tranVideoServer(conn *net.UDPConn, pChanData *RecvUdpChanDataType) {
	mediaHead := getMediaHeadStructFromSlice(pChanData.buf[protoHeadSize:mediaHeadSize])

	upDownRwLock.RLock()
	key := UpDownParaKeyType{string(mediaHead.mac[0:]), mediaHead.streamId}
	value, ok := upDownDatas[key]
	if ok == true {
		value.vChan <- pChanData.buf[0:pChanData.packageSize]
	}
	upDownRwLock.RUnlock()
}

func tranAudioServer(conn *net.UDPConn, pChanData *RecvUdpChanDataType) {
	mediaHead := getMediaHeadStructFromSlice(pChanData.buf[protoHeadSize:mediaHeadSize])

	upDownRwLock.RLock()
	key := UpDownParaKeyType{string(mediaHead.mac[0:]), mediaHead.streamId}
	value, ok := upDownDatas[key]
	if ok == true {
		value.aChan <- pChanData.buf[0:pChanData.packageSize]
	}
	upDownRwLock.RUnlock()
}

func recvUdpShakeHandPackage(conn *net.UDPConn, pChanData *RecvUdpChanDataType) {
	b := pChanData.buf[0:]
	head := &ProtoHeadType{uint16(b[0]) | uint16(b[1])<<8, uint16(b[2]) | uint16(b[3])<<8, uint16(b[4]) | uint16(b[5])<<8, uint16(b[6]) | uint16(b[7])<<8}
	if head.bodySize != uint16(macAddrBytes) || b[protoHeadSize+int(macAddrBytes)-1] != 0 {
		fmt.Println("recvUdpShakeHandPackage: head error ! bodySize=", head.bodySize, "last=", b[protoHeadSize+int(macAddrBytes)-1])
		return
	}

	addr := &pChanData.addr
	fmt.Println("recv a udp shake hand package !", addr.String())

	mac := getMacFromBytesSlice(b[protoHeadSize:])

	addMacUdpAddrToMap(&mac, addr)

	c := b[0 : protoHeadSize+int(macAddrBytes)]
	n, err := conn.WriteToUDP(c, addr)
	if err != nil {
		fmt.Println("send back handshake package fail ! err = ", err.Error(), "n = ", n)
		return
	}
}

func recvUdpWaveHandPackage(conn *net.UDPConn, pChanData *RecvUdpChanDataType) {
	b := pChanData.buf[0:]
	head := &ProtoHeadType{uint16(b[0]) | uint16(b[1])<<8, uint16(b[2]) | uint16(b[3])<<8, uint16(b[4]) | uint16(b[5])<<8, uint16(b[6]) | uint16(b[7])<<8}
	if head.bodySize != uint16(macAddrBytes) || b[protoHeadSize+int(macAddrBytes)-1] != 0 {
		fmt.Println("recvUdpShakeHandPackage: head error ! bodySize=", head.bodySize, "last=", b[protoHeadSize+int(macAddrBytes)-1])
		return
	}

	addr := &pChanData.addr
	fmt.Println("recv a udp wave hand package !", addr.IP, addr.Port)

	mac := getMacFromBytesSlice(b[protoHeadSize:])

	clearMacUdpInMap(&mac, addr)

	c := b[0 : protoHeadSize+int(macAddrBytes)]
	n, err := conn.WriteToUDP(c, addr)
	if err != nil {
		fmt.Println("send back handshake package fail ! err = ", err.Error(), "n = ", n)
		return
	}
}

func addMacUdpAddrToMap(mac *MacType, udpAddr *net.UDPAddr) {
	m := string(mac[0:])
	v := new(MapValueType)
	v.udpAddr = *udpAddr

	mapRwLock.Lock()
	value, ok := macUdpTcpMap[m]
	if ok == true {
		fmt.Println("there is an old mac in map ! refresh it's udpAddr ! mac=", m, "old udp=", value.udpAddr, "tcp=", value.tcpAddrString, "new udp=", *udpAddr)
		v.tcpAddrString = value.tcpAddrString
	} else {
		fmt.Println("add a new mac to map ! mac=", m, "udp=", *udpAddr)
		v.tcpAddrString = ""
	}
	macUdpTcpMap[m] = v
	mapRwLock.Unlock()
}

func clearMacUdpInMap(mac *MacType, udpAddr *net.UDPAddr) {
	m := string(mac[0:])

	mapRwLock.Lock()
	value, ok := macUdpTcpMap[m]
	if ok == false {
		fmt.Println("can not delete mac from map because of no this mac in map ! mac=", m)
	} else {
		if isUdpAddrEqual(udpAddr, &(value.udpAddr)) == false {
			fmt.Println("can not delete mac from map because of udpAddr is not match ! mac=", m, "save udp=", value.udpAddr, "aim udp=", *udpAddr)
		} else {
			fmt.Println("can delete mac from map ! mac=", m, "udp=", *udpAddr)
			delete(macUdpTcpMap, m)
		}
	}
	mapRwLock.Unlock()
}

func getUdpAddrFromMapAccordingToMac(mac *MacType) (net.UDPAddr, int) {
	m := string(mac[0:])
	var udpAddr net.UDPAddr
	var err int
	mapRwLock.RLock()
	value, ok := macUdpTcpMap[m]
	if ok == false {
		err = -1
	} else {
		err = 0
		udpAddr = value.udpAddr
	}
	mapRwLock.RUnlock()

	return udpAddr, err
}

func getUdpAddrFromMapAccordingToTcpAddrString(tcpAddrString string) (net.UDPAddr, int) {
	var udpAddr net.UDPAddr
	var err int
	err = -1

	mapRwLock.RLock()
	for _, v := range macUdpTcpMap {
		if strings.Compare(v.tcpAddrString, tcpAddrString) == 0 {
			err = 0
			udpAddr = v.udpAddr
			break
		}
	}
	mapRwLock.RUnlock()

	return udpAddr, err
}

func getTcpStringAccordingToMac(mac *MacType) string {
	m := string(mac[0:])
	var tcpAddrString string

	mapRwLock.RLock()
	value, ok := macUdpTcpMap[m]
	if ok == false {
		tcpAddrString = ""
	} else {
		tcpAddrString = value.tcpAddrString
	}
	mapRwLock.RUnlock()

	return tcpAddrString
}

func getMacAccordingToTcpAddrString(tcpAddrString string) (MacType, int) {
	var mac MacType
	var err int
	err = -1

	mapRwLock.RLock()
	for k, v := range macUdpTcpMap {
		if strings.Compare(v.tcpAddrString, tcpAddrString) == 0 {
			mac = getMacFromBytesSlice([]byte(k))
			err = 0
		}

	}
	mapRwLock.RUnlock()

	return mac, err
}

func addTcpConnToMapAccordingToMac(mac *MacType, tcpAddrString string) int {
	//fmt.Println("into addTcpConnToMapAccordingToMac")

	m := string(mac[0:])
	var err int

	mapRwLock.RLock()
	value, ok := macUdpTcpMap[m]
	if ok == false {
		err = -1
	} else {
		err = 0
		v := new(MapValueType)
		v.udpAddr = value.udpAddr
		v.tcpAddrString = tcpAddrString
		macUdpTcpMap[m] = v
	}
	mapRwLock.RUnlock()

	fmt.Println("fail to add", string(mac[0:]), tcpAddrString)
	return err
}

func deleteTcpConnToMapAccordingToMac(mac *MacType, tcpAddrString string) int {
	m := string(mac[0:])
	var err int

	mapRwLock.Lock()
	value, ok := macUdpTcpMap[m]
	if ok == false {
		err = -1
	} else {
		if strings.Compare(value.tcpAddrString, tcpAddrString) != 0 {
			err = -2
		} else {
			err = 0
			noticeClientOutLine(mac)
			delete(macUdpTcpMap, m)
		}

	}
	mapRwLock.Unlock()

	fmt.Println("fail to delete", string(mac[0:]), tcpAddrString)
	return err
}

func noticeClientOutLineByTcpAddrString(tcpAddrString string) {
	mac, err := getMacAccordingToTcpAddrString(tcpAddrString)
	if err != 0 {
		fmt.Println("noticeClientOutLineByTcpAddrString:getMacAccordingToTcpAddrString fail !", tcpAddrString)
		return
	}

	noticeClientOutLine(&mac)
}

func noticeClientOutLine(mac *MacType) {
	saveUdpAddr, err := getUdpAddrFromMapAccordingToMac(mac)
	if err != 0 {
		fmt.Println("noticeClientOutLine: not mark mac !", *mac)
		return
	}

	upDownRwLock.Lock()
	for key := range upDownDatas {
		e := upDownDatas[key]
		if e.busy == true {
			if strings.Compare(key.m, string(mac[0:])) == 0 {
				e.busy = false
			} else {
				for j := range e.downLoadDatas {
					f := &(e.downLoadDatas[j])
					if f.busy == true && isUdpAddrEqual(&(f.udpAddr), &saveUdpAddr) == true {
						f.busy = false
						break
					}
				}
			}
		}
	}
	upDownRwLock.Unlock()
}

func udpClientPushStream(mac *MacType, streamId int, video bool, audio bool, tcpAddrString string, videoPara *PushVideoParaType, audioPara *PushAudioParaType) int {
	saveTcpAddrString := getTcpStringAccordingToMac(mac)
	if saveTcpAddrString == "" || saveTcpAddrString != tcpAddrString {
		fmt.Println("udpClientPushStream: saveTcpAddrString or tcpAddrString error !", saveTcpAddrString, tcpAddrString)
		return -1
	}

	ret := 0
	key := UpDownParaKeyType{string(mac[0:]), streamId}

	upDownRwLock.Lock()
	_, ok := upDownDatas[key]
	if ok == true {
		e := upDownDatas[key]
		e.video = e.video || video
		e.audio = e.audio || audio
		e.videoPara = *videoPara
		e.audioPara = *audioPara
		fmt.Println("same upload client !", key.m, key.streamId)
	} else {
		upDownDatas[key] = &UpDownParaValueType{}
		e := upDownDatas[key]
		e.busy = true
		e.video = video
		e.audio = audio
		e.videoPara = *videoPara
		e.audioPara = *audioPara
		e.vChan = make(chan []byte, transitChanSize)
		e.aChan = make(chan []byte, transitChanSize)
		fmt.Println("new upload client !", key.m, key.streamId)
		for j := range e.downLoadDatas {
			e.downLoadDatas[j].busy = false
		}

		go videoTransitThread(&udpListenConn, e.vChan, e)
		go audioTransitThread(&udpListenConn, e.aChan, e)
	}
	upDownRwLock.Unlock()

	if ok == false {
		ret = -1
	}

	return ret
}

func udpClientCanclePushStream(mac *MacType, streamId int, video bool, audio bool, tcpAddrString string) int {
	saveTcpAddrString := getTcpStringAccordingToMac(mac)
	if saveTcpAddrString == "" || saveTcpAddrString != tcpAddrString {
		fmt.Println("udpClientCanclePushStream: saveTcpAddrString or tcpAddrString error !", saveTcpAddrString, tcpAddrString)
		return -1
	}

	var ret int
	m := string(mac[0:])
	mapRwLock.RLock()
	mapv, mapok := macUdpTcpMap[m]
	if mapok == false {
		fmt.Println("cancle push: can not find client in map !\n", m)
		ret = -1
	} else {
		if strings.Compare(mapv.tcpAddrString, tcpAddrString) != 0 {
			fmt.Println("cancle push: tcp not match in map !\n", m, mapv.tcpAddrString, tcpAddrString)
			ret = -2
		} else {
			ret = 0
		}
	}
	mapRwLock.RUnlock()

	if ret < 0 {
		return ret
	}

	key := UpDownParaKeyType{m, streamId}
	upDownRwLock.Lock()
	_, ok := upDownDatas[key]
	if ok == false {
		fmt.Println("cancle push: can not find client in upDown!", key.m, key.streamId)
		ret = -3
	} else {
		e := upDownDatas[key]
		if e.video == true {
			e.video = !video
		}
		if e.audio == true {
			e.audio = !audio
		}
		if e.video == false && e.audio == false {
			e.busy = false
			//need to wait goroutine return
			time.Sleep(1 * time.Millisecond)
			delete(upDownDatas, key)
		}

		fmt.Println("clear upload client !", key.m, key.streamId)
		ret = 0
	}
	upDownRwLock.Unlock()

	return ret
}

func udpClientPullStream(mac *MacType, streamId int, video bool, audio bool, tcpAddrString string) (int, int, int, PushVideoParaType, PushAudioParaType) {
	var videoPara PushVideoParaType
	var audioPara PushAudioParaType
	var v int
	var a int
	v = 0
	a = 0

	m := string(mac[0:])
	key := UpDownParaKeyType{m, streamId}
	_, err := getUdpAddrFromMapAccordingToMac(mac)
	if err != 0 {
		fmt.Println("udpClientPullStream: can not get srcUdpAddr ! mac=", string(mac[0:]))
		return -1, v, a, videoPara, audioPara
	}

	dstUdpAddr, err := getUdpAddrFromMapAccordingToTcpAddrString(tcpAddrString)
	if err != 0 {
		fmt.Println("udpClientPullStream: can not get dstUdpAddr ! tcpAddrString=", tcpAddrString)
		return -2, v, a, videoPara, audioPara
	}

	ret := -3
	upDownRwLock.Lock()
	_, ok := upDownDatas[key]
	if ok == false {
		fmt.Println("can not pull ! not this upload !", key.m, key.streamId, dstUdpAddr)
	} else {
		fmt.Println("udpClientPullStream:upload info match ! prepare to check download list !", key.m, key.streamId, dstUdpAddr)
		e := upDownDatas[key]
		for i := range e.downLoadDatas {
			f := &(e.downLoadDatas[i])
			if f.busy == true && isUdpAddrEqual(&(f.udpAddr), &dstUdpAddr) == true {
				fmt.Println("udpClientPullStream:same download client ! prepare to refresh !", i, dstUdpAddr, key.m, key.streamId)
				f.video = f.video || video
				f.audio = f.audio || audio
				ret = 0
				videoPara = e.videoPara
				audioPara = e.audioPara
				if e.video == true && f.video == true {
					v = 1
				} else {
					v = 0
				}
				if e.audio == true && f.audio == true {
					a = 1
				} else {
					a = 0
				}
				break
			}
		}

		if ret < 0 {
			for i := range e.downLoadDatas {
				f := &(e.downLoadDatas[i])
				if f.busy == false {
					fmt.Println("add new download client ! prepare to refresh !", dstUdpAddr, key.m, key.streamId, dstUdpAddr)
					f.busy = true
					f.video = video
					f.audio = audio
					f.udpAddr = dstUdpAddr
					ret = i
					videoPara = e.videoPara
					audioPara = e.audioPara
					if e.video == true && f.video == true {
						v = 1
					} else {
						v = 0
					}
					if e.audio == true && f.audio == true {
						a = 1
					} else {
						a = 0
					}
					break
				}
			}
		}
	}

	upDownRwLock.Unlock()

	if ret < 0 {
		fmt.Println("can not pull ! no download space !", key.m, key.streamId, dstUdpAddr)
	} else {
		ret = 0
	}

	return ret, v, a, videoPara, audioPara
}

func udpClientCanclePullStream(mac *MacType, streamId int, video bool, audio bool, tcpAddrString string) int {
	_, err := getUdpAddrFromMapAccordingToMac(mac)
	if err != 0 {
		fmt.Println("udpClientPullStream: can not get srcUdpAddr ! mac=", string(mac[0:]))
		return -1
	}

	dstUdpAddr, err := getUdpAddrFromMapAccordingToTcpAddrString(tcpAddrString)
	if err != 0 {
		fmt.Println("udpClientPullStream: can not get dstUdpAddr ! tcpAddrString=", tcpAddrString)
		return -2
	}

	m := string(mac[0:])
	key := UpDownParaKeyType{m, streamId}
	ret := -3

	upDownRwLock.Lock()
	_, ok := upDownDatas[key]
	if ok == false {
		fmt.Println("no this upload !", key, tcpAddrString, dstUdpAddr)
	} else {
		e := upDownDatas[key]
		for i := range e.downLoadDatas {
			f := &(e.downLoadDatas[i])
			if f.busy == true && isUdpAddrEqual(&(f.udpAddr), &dstUdpAddr) == true {
				fmt.Println("udpClientCanclePullStream:cancle pull !", dstUdpAddr)
				if f.video == true {
					f.video = !video
				}
				if f.audio == true {
					f.audio = !audio
				}
				if f.video == false && f.audio == false {
					f.busy = false
				}
				ret = 0
				break
			}
		}
	}
	upDownRwLock.Unlock()

	return ret
}
